#include "stdafx.h"
#include "U7Objs.h"
#include "MapChunk.h"
#include "WorldMap.h"
#include "U7FilesLoader.h"
#include "Shape.h"
#include "DimInfo.h"

#include "U7Objs.inl"

#include <algorithm>

MapChunk::MapChunk( WorldMap* map, int chx, int chy )
{
	worldMap = map;
	xchunk = chx;
	ychunk = chy;

	firstNonFlat = objects.begin();

	refFromBelow = 0;
	refFromRight = 0;
	refFromBelowRight = 0;

	patternID = 0xFFFF;
}

MapChunk::~MapChunk()
{
	for ( OBJITR itr = objects.begin(); itr != objects.end(); ++itr )
	{
		delete (*itr);
	}
	objects.clear();
}

void MapChunk::ResetTerrain()
{
	// remove non-tile objects in the old pattern
	CHUNK_PATTERN chunkPattern = (CHUNK_PATTERN)worldMap->u7Files->chunkFile;
	SHAPEID (*pattern)[CHUNKS_PER_SUPERCHUNK_IN_WIDTH] = chunkPattern[patternID];
	SHAPEID* shapeID = &pattern[0][0];

	for ( int i = 0; i < TILES_PER_CHUNK; i++ )
	{
		long shapeLength;
		PSHAPEENTRY shapeEntry = GetShape( worldMap->u7Files->shapeFile,
			shapeID->shapeType, &shapeLength );

		if ( !IsShapeTile( shapeEntry, shapeLength ) )
		{
			BasicObject* obj = Find(
				i%TILES_PER_CHUNK_IN_WIDTH,
				i/TILES_PER_CHUNK_IN_WIDTH,
				0,
				*shapeID );
			assert(obj);
			Remove(obj);
			delete obj;
		}
		shapeID++;
	}
}

void MapChunk::SetTerrain( PATTERN_ID pid )
{
	assert(pid<0xC00);
	if ( patternID != 0xFFFF )
	{
		ResetTerrain();
	}

	patternID = pid;	

	// add non-tile objects
	CHUNK_PATTERN chunkPattern = (CHUNK_PATTERN)worldMap->u7Files->chunkFile;
	SHAPEID (*pattern)[CHUNKS_PER_SUPERCHUNK_IN_WIDTH] = chunkPattern[patternID];
	SHAPEID* shapeID = &pattern[0][0];

#if 0
	// for check
	if ( xchunk != 78 || ychunk != 43 )
		return;
#endif

	for ( int y = 0; y < TILES_PER_CHUNK_IN_HEIGHT; y++ )
	{
		for ( int x = 0; x < TILES_PER_CHUNK_IN_WIDTH; x++ )
		{
			long shapeLength;
			PSHAPEENTRY shapeEntry = GetShape( worldMap->u7Files->shapeFile,
				shapeID->shapeType, &shapeLength );

			if ( !IsShapeTile( shapeEntry, shapeLength ) )
			{
				const TFA* pTFA = GetShapeTFA( worldMap->u7Files->tfaFile, shapeID->shapeType );
				BasicObject* obj = pTFA->animated
					? new AnimatedTerrainObject(worldMap,*shapeID)
					: new TerrainObject(worldMap,*shapeID);
				Add(obj,x,y,0);
			}

			shapeID++;
		}
	}
}

void MapChunk::SetIfixObjects( int count, const IFIXENTRY* entry )
{
#if 0
	// for check
	// if ( xchunk != 16 || ychunk != 15 )
	if ( !(15<=xchunk && xchunk < 16) || !(15<=ychunk && ychunk<16) )
		return;
#endif

	for ( ; count-- > 0; entry++ )
	{
#ifdef DEBUG
		long shapeLength;
		PSHAPEENTRY shapeEntry = GetShape( worldMap->u7Files->shapeFile, entry->shapeID.shapeType, &shapeLength );
		int frameCount = GetShapeFrameCount( shapeEntry, shapeLength );

		assert( entry->shapeID.shapeFrame < frameCount );
#endif


#if 0
		if ( !(entry->xt==6&&entry->yt==6&&entry->zlevel==1) &&
			!(entry->xt==10&&entry->yt==4&&entry->zlevel==1) &&
			!(entry->xt==10&&entry->yt==6&&entry->zlevel==0) &&
			!(entry->xt==6&&entry->yt==6&&entry->zlevel==0) )
			continue;
#endif
		BasicObject* obj = new IfixObject(worldMap,entry->shapeID);
		Add(obj,entry->xt,entry->yt,entry->zlevel);
	}
}

void MapChunk::Add( BasicObject* newObj, int x, int y, int z )
{
	// new object should not be in the list
	assert( objects.end() == std::find(objects.begin(), objects.end(), newObj) );
	// new object should not be owned by any map chunk.
	assert( newObj->GetMapChunk() == NULL );

	// set the new object to be owned by this map chunk and set its tile coords.
	newObj->SetMapChunk(this);
	newObj->SetLocalCoords(x,y,z);
	
	U7VECTOR3 localCoords, tileSizes;
	newObj->GetLocalCoords(&localCoords);
	newObj->GetTileSizes(&tileSizes);

	objects.insert( firstNonFlat, newObj ); // insert this object before firstNonFlat

	// if the object is not a flat object or its zlevel is greater than 0,
	// need to check dependencies
	if ( localCoords.z > 0 || tileSizes.z > 0 ) // z_level != 0 or z_height != 0
	{
		// check dependencies in  this chunk
		// compare objects between firstNonFlat and objects.end()
		UpdateDependencies(newObj);

		--firstNonFlat; // set the firstNonFlat to this new object

		// this chunk is onwed by the map
		if ( xchunk != 0xFF && ychunk != 0xFF )
		{
			MapChunk* checked = NULL;
			//
			// chunk at below
			int below = INCR_CHUNK(ychunk);		
			if ( refFromBelow )
			{
				checked = worldMap->GetMapChunk( xchunk, below );
				checked->UpdateDependencies(newObj);
			}
			// chunk at right
			int right = INCR_CHUNK(xchunk);
			if ( refFromRight )
			{
				checked = worldMap->GetMapChunk( right, ychunk );
				checked->UpdateDependencies(newObj);
			}
			// chunk at below right
			if ( refFromBelowRight )
			{
				checked = worldMap->GetMapChunk( right, below );
				checked->UpdateDependencies(newObj);
			}

			// add references to the adjacent chunks
			// extend to left or above?
			bool ext_left = (localCoords.x - tileSizes.x + 1) < 0;
			bool ext_above = (localCoords.y - tileSizes.y + 1) < 0;
			int left = DECR_CHUNK(xchunk);
			int above = DECR_CHUNK(ychunk);
			if ( ext_left )
			{
				checked = worldMap->GetMapChunk( left, ychunk );
				checked->UpdateDependencies(newObj);
				checked->refFromRight++;

				if ( ext_above )
				{
					checked = worldMap->GetMapChunk( left, above );
					checked->UpdateDependencies(newObj);
					checked->refFromBelowRight++;
				}
			}
			if ( ext_above )
			{
				checked = worldMap->GetMapChunk( xchunk, above );
				checked->UpdateDependencies(newObj);
				checked->refFromBelow++;
			}
		}
	}
}

bool MapChunk::Remove( BasicObject* removedObj )
{
	// the removed object should be owned by this map chunk
	if ( removedObj->GetMapChunk() != this )
	{
		assert(false);
		return false;
	}

	// the removed object should be in the list
	OBJITR objItr = std::find(objects.begin(), objects.end(), removedObj);
	if ( objItr == objects.end() )
	{
		assert(false);
		return false;
	}

	// remove references to adjacent chunks
	if ( xchunk != 0xFF && ychunk != 0xFF )
	{
		U7VECTOR3 localCoords, tileSizes;
		removedObj->GetLocalCoords(&localCoords);
		removedObj->GetTileSizes(&tileSizes);

		// extend to left or above?
		bool ext_left = (localCoords.x - tileSizes.x + 1) < 0;
		bool ext_above = (localCoords.y - tileSizes.y + 1) < 0;
		int left = DECR_CHUNK(xchunk);
		int above = DECR_CHUNK(ychunk);
		if ( ext_left )
		{
			worldMap->GetMapChunk( left, ychunk )->refFromBelow--;
			if ( ext_above )
			{
				worldMap->GetMapChunk( left, above )->refFromBelowRight--;
			}
		}
		if ( ext_above )
		{
			worldMap->GetMapChunk( xchunk, above )->refFromBelow--;
		}
	}

	// change firstNonFlat if it is the removed object.
	if ( firstNonFlat != objects.end() && (*firstNonFlat) == removedObj )
	{
		++firstNonFlat;
	}

	objects.erase(objItr);
	removedObj->ClearDependencies();	

	removedObj->SetLocalCoords(0,0,0);
	removedObj->SetMapChunk(NULL);

	return true;
}

inline bool operator == ( const SHAPEID& id1, const SHAPEID& id2 )
{
	return (id1.shapeType == id2.shapeType && id1.shapeFrame == id2.shapeFrame);
}

BasicObject* MapChunk::Find( int xt, int yt, int zlevel, const SHAPEID& shapeID ) const
{
	U7VECTOR3 local;
	COBJITR itr = std::find_if( objects.cbegin(), objects.cend(),
		[&]( const BasicObject* obj)->bool {
			obj->GetLocalCoords(&local);
			return xt == local.x && yt == local.y && zlevel == local.z && obj->ShapeID() == shapeID;
	});

	return itr != objects.end() ? *itr : NULL;
}

void MapChunk::HitTest( int worldX, int worldY, int zlimit, OBJLIST& hits ) const
{
	for ( COBJITR objItr = objects.begin();
		objItr != objects.end();
		++objItr )
	{
		if ( (*objItr)->HitTest(worldX,worldY,zlimit) )
		{
			// sorting, the closer one to the user is placed in the front of the list.
			DimInfo diThis(*(*objItr));
			COBJITR itrInsert = std::find_if( hits.cbegin(), hits.cend(),
				[&]( const BasicObject* another)->bool {
					DimInfo diAnother(*another);
					return diThis.Compare(diAnother) >= 0;
			});
			hits.insert( itrInsert, *objItr );
		}
	}
}
